package device

import (
	"fmt"
	"micro-rulego/internal/biz/hardware"
	"micro-rulego/internal/cache"
	consul "micro-rulego/pkg/consul/cache"
	"sync"

	"github.com/go-kratos/kratos/v2/log"
	"github.com/google/uuid"
)

type connectRecord struct {
	w       cache.Watcher
	c       cache.Cache
	spec    []byte
	checked bool
}

type connectControllerUsecase struct {
	devHandle []byte
	hwctrl    hardware.Controller
	idconn    map[ConnectID]*connectRecord
	m         sync.RWMutex
}

func (c *connectControllerUsecase) ConnectCreate(connData []byte) error {
	return c.hwctrl.ConnectRequest(c.devHandle, connData)
}

func (c *connectControllerUsecase) ConnectDestroy(connID ConnectID) error {
	c.m.RLock()
	defer c.m.RUnlock()
	connRec, exists := c.idconn[connID]
	if !exists {
		return ErrConnectRecordNotFound
	}
	return c.hwctrl.ConnectFree(c.devHandle, connRec.spec)
}

func (c *connectControllerUsecase) ConnectTransimit(connID ConnectID, data []byte) error {
	c.m.RLock()
	defer c.m.RUnlock()
	connRec, exists := c.idconn[connID]
	if !exists {
		return ErrConnectRecordNotFound
	}
	return c.hwctrl.ConnectTransimit(c.devHandle, connRec.spec, data)
}

func (c *connectControllerUsecase) ConnectReceive(connID ConnectID) ([]byte, error) {
	c.m.RLock()
	defer c.m.RUnlock()
	connRec, exists := c.idconn[connID]
	if !exists {
		return nil, ErrConnectRecordNotFound
	}
	return c.hwctrl.ConnectReceive(c.devHandle, connRec.spec)
}

func (c *connectControllerUsecase) connectWatcherTask(connID ConnectID) {
	c.m.RLock()
	connRec, exist := c.idconn[connID]
	c.m.RUnlock()
	if !exist {
		log.Error("Watcher for connect", ErrConnectRecordNotFound)
		return
	}
	//skip the first event
	connRec.w.Next()
	for {
		cs, err := connRec.w.Next()
		if err != nil && err != cache.ErrWatcherStopped {
			connRec.w.Stop()
			log.Error("Watcher for connect", err)
			return
		} else if err == cache.ErrWatcherStopped {
			log.Debug("Watcher for connect", connID, "stopped")
			return
		}
		err = c.hwctrl.ConnectConfigure(c.devHandle, connRec.spec, cs.Data)
		if err != nil {
			log.Error("Configure connect", connID, "fail")
		} else {
			log.Info("Configure connect", connID, "success")
		}
	}
}

func (c *connectControllerUsecase) ConnectCheck() (map[ConnectID]*ConnectStatus, error) {
	hcdata, err := c.hwctrl.ConnectCheck(c.devHandle)
	if err != nil {
		return nil, err
	}
	c.m.Lock()
	defer c.m.Unlock()
	for _, v := range c.idconn {
		v.checked = false
	}
	cstatus := make(map[ConnectID]*ConnectStatus)
	for _, v := range hcdata {
		connID := uuid.NewSHA1(uuid.NameSpaceURL, v.Spec()).ID()
		cstatus[ConnectID(connID)] = &ConnectStatus{
			IsAvaliable: v.IsAvaliable(),
			Spec:        v.Spec(),
		}
		connRec, exist := c.idconn[ConnectID(connID)]
		if exist {
			//if connect has been record,skip
			connRec.checked = true
			continue
		}

		//it is the first time to check this connect, start config watcher
		configpath := fmt.Sprintf(
			"%d/%d",
			uuid.NewSHA1(uuid.NameSpaceURL, c.devHandle).ID(), connID,
		)

		cache := consul.NewConsulCache(consul.WithKey(configpath))
		err := cache.Write(v.PreConfig())
		if err != nil {
			return nil, err
		}
		w, err := cache.Watch()
		if err != nil {
			return nil, err
		}

		c.idconn[ConnectID(connID)] = &connectRecord{
			w:       w,
			c:       cache,
			spec:    v.Spec(),
			checked: true,
		}

		go c.connectWatcherTask(ConnectID(connID))
	}

	//find the connect has been invalid,remove their record
	for id, v := range c.idconn {
		if !v.checked {
			v.w.Stop()
			delete(c.idconn, id)
			v.c.Remove()
		}
	}
	return cstatus, nil
}

func newConnectController(deivceHandle []byte, h hardware.Controller) connectController {
	return &connectControllerUsecase{
		devHandle: deivceHandle,
		hwctrl:    h,
		idconn:    make(map[ConnectID]*connectRecord),
	}
}
